/**
 * 
 */
package cn.springsoter.core.security.interceptor;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import cn.hutool.core.util.CharsetUtil;
import cn.springsoter.core.security.helper.CryptoHelper;
import cn.springsoter.core.security.strategy.impl.ReadAESKeyDefaultStrategy;
import cn.springsoter.core.security.strategy.impl.ReadAESKeyStoreStrategy;
import cn.springsoter.core.tool.utils.AESUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * Mybatis拦截器：加密拦截器，入库前加密，只支持String类型的字段
 * 
 * @author kevin
 *
 */
@Slf4j
@Component
@Intercepts({
		/*
		 * // 语句执行拦截
		 * Executor (update, query, flushStatements, commit, rollback, getTransaction, 
		 * close, isClosed) 
		 * // 参数获取、设置时进行拦截
		 * ParameterHandler (getParameterObject, setParameters) 
		 * // 对返回结果进行拦截
		 * ResultSetHandler (handleResultSets, handleOutputParameters) 
		 * // sql语句拦截
		 * StatementHandler (prepare, parameterize, batch, update, query)
		 */
		@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) })
public class EncryptInterceptor implements Interceptor {

	@Autowired
	private ReadAESKeyDefaultStrategy readAESKeyDefaultStrategy;
	@Autowired
	private ReadAESKeyStoreStrategy readAESKeyStoreStrategy;
	
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// @Signature 指定了 type= parameterHandler 后，这里的 invocation.getTarget()
		// 便是parameterHandler
		// 若指定ResultSetHandler，这里则能强转为ResultSetHandler
		ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
		// 获取参数对像，即 mapper 中 paramsType 的实例
		Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
		// 设置允许访问parameterObject属性
		parameterField.setAccessible(true);
		/* 
		 * 获取当前正在被操作的类, 有可能是Java Bean, 也可能是普通的操作对象, 比如普通的参数传递    
	     * 普通参数, 即是 @Param 包装或者原始 Map 对象, 普通参数会被 Mybatis 包装成 Map 对象    
	     * 即是 org.apache.ibatis.binding.MapperMethod$ParamMap
	     */  
		Object parameterObject = parameterField.get(parameterHandler);
		if (Objects.nonNull(parameterObject)) {
			Object bizObj = parameterObject;
			
			Class<?> parameterObjectClass = null;
			if(parameterObject instanceof Map) { 
				Map<String, Object> map = (Map<String, Object>) parameterObject; 
				String firstKey = map.keySet().iterator().next();
				bizObj = map.get(firstKey);
				if(Objects.nonNull(bizObj)) {
					parameterObjectClass = bizObj.getClass();
				} else {
					return invocation.proceed();
				}
			} else {
				parameterObjectClass = parameterObject.getClass();
			}
        	// 取出当前当前类所有字段，传入加密方法
        	Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        	for (Field field : declaredFields) {
        		encrypt(bizObj, field);
        	}
		}
		return invocation.proceed(); //这一步会调用对应的TypeHandler
	}

	/**
	 * 加密
	 * 
	 * @param parameterObject
	 * @param field		字段
	 * @throws IllegalAccessException
	 */
	private void encrypt(Object parameterObject, Field field) throws Exception {
		// 校验该字段是否被@SensitiveInfo所注解，并且needCrypto=true
		if (CryptoHelper.needCrypto(field)) {
			field.setAccessible(true);
			Object object = field.get(parameterObject);
			// 暂时只实现String类型的加密
			if (object instanceof String) {
				String value = (String) object;
				log.debug("======mybatis拦截器（EncryptInterceptor）执行中，拦截的字段名是：{}，值是：{}", field.getName(), value);

				if (StringUtils.isNotBlank(value)) {
					String key = StringUtils.isBlank(readAESKeyStoreStrategy.readKey()) ? readAESKeyDefaultStrategy.readKey() : readAESKeyStoreStrategy.readKey();
					if(StringUtils.isNotBlank(key)) {
						try {
							value = AESUtil.encryptByECBBase64(key.getBytes(CharsetUtil.CHARSET_UTF_8), value.getBytes(CharsetUtil.CHARSET_UTF_8));
						} catch (Exception e) {}
					}
				}
				field.set(parameterObject, value);
			}
		}
	}
}
